/**
 * 
 * 
 * 
 */
import Viewer from "../src/viewer";
import defined from "../src/core/defined";
import BoxGeometry from "../src/geometry/boxGeometry"
import SphereGeometry from "../src/geometry/sphereGeometry";

import { createShadowPipeline } from "../src/pipeline/pipelineShadow";
import { createGBuffer, createGeometryPipeline } from "../src/pipeline/pipelineGeometry"
import { createLightingPipeline } from "../src/pipeline/pipelineLight"

import normal from '../src/shader/normal.vert.wgsl?raw'
import lambert from '../src/shader/lambert.frag.wgsl?raw'
import emissive from '../src/shader/emissive.frag.wgsl?raw'
import { getModelViewMatrix, getProjectionMatrix } from '../src/util/math'
import GUI from 'lil-gui';

import ViewControls from "../src/viewControls";

import { vec3, mat4 } from 'gl-matrix'

const boxNum = 2000;
const sphereNum = 2000;

const NUM = boxNum + sphereNum;
let geomNum = NUM + 2;
let lightNUM = 160;
const maxPointLightIntensity = 1

const width = 1000;
const height = 30;

const lightParams = {
    ambientIntensity: 0.2,
    pointLightIntensity: maxPointLightIntensity,
    pointLightRadius: 40,
    directionalLightIntensity: 0.5,

    pointLightNum: lightNUM,
    lightsMove: false,
    lightsMoveSpeed: 10,

    enableShadow: true,
};
const gui = new GUI({
    title: "灯光参数"
});

const viewer = new Viewer('viewer', {});
await viewer.init();

const scene = viewer.getScene();
if (defined(scene)) {

    lightParams.enableShadow = scene.enableShadow;

    const device = scene.getDevice();
    const format = scene.getFormat();
    const size = scene.getSize();

    // 在初始化时创建管线

    let gbuffer = createGBuffer(device, size);

    const cullMode: GPUCullMode = 'back';

    const geometryPipelineObj = await createGeometryPipeline(
        device, { vertex: normal, fragment: lambert }, size, geomNum, gbuffer, cullMode);

    const box = new BoxGeometry(1, 1, 1, geometryPipelineObj);
    const sphere = new SphereGeometry(1, 30, 30, geometryPipelineObj);
    scene.addInstance(sphere, sphereNum);
    scene.addInstance(box, boxNum + 2);

    let emissiveGeometryPipelineObj = await createGeometryPipeline(
        device, { vertex: normal, fragment: emissive }, size, lightNUM, gbuffer, cullMode);
    let sphereEmissive = new SphereGeometry(0.3, 30, 30, emissiveGeometryPipelineObj);
    scene.addInstance(sphereEmissive, lightNUM);


    const modelViewMatrix = new Float32Array(geomNum * 4 * 4)
    const colorBuffer = new Float32Array(geomNum * 4)
    for (let i = 0; i < NUM; i++) {
        // create simple object
        const position = { x: Math.random() * width - width / 2, y: Math.random() * height - height / 2, z: Math.random() * width - width / 2 }
        const rotation = { x: Math.random(), y: Math.random(), z: Math.random() }

        const s = Math.random() * 3 + 1;;
        const scale = { x: s, y: s, z: s }
        const modelView = getModelViewMatrix(position, rotation, scale)
        modelViewMatrix.set(modelView, i * 4 * 4)
        // random color for each object
        colorBuffer.set([Math.random(), Math.random(), Math.random(), 1], i * 4)
        // colorBuffer.set([1, 1, 1, 1], i * 4)
    }

    {
        let i = NUM;
        // create simple object
        let position = { x: 0, y: -height / 2 - 1, z: 0 }
        let rotation = { x: 0, y: 0, z: 0 }
        let scale = { x: width, y: 1, z: width }
        let modelView = getModelViewMatrix(position, rotation, scale)
        modelViewMatrix.set(modelView, i * 4 * 4)
        // random color for each object
        colorBuffer.set([0.6, 0.6, 0.6, 1], i * 4)

        i = NUM + 1;
        // create simple object
        position = { x: 0, y: 0, z: 0 }
        rotation = { x: 0, y: 0, z: 0 }
        scale = { x: 10, y: height, z: 10 }
        modelView = getModelViewMatrix(position, rotation, scale)
        modelViewMatrix.set(modelView, i * 4 * 4)
        // random color for each object
        colorBuffer.set([1, 1, 1, 1], i * 4)
    }

    // write matrix & colors
    device.queue.writeBuffer(geometryPipelineObj.colorBuffer, 0, colorBuffer)
    device.queue.writeBuffer(geometryPipelineObj.modelViewBuffer, 0, modelViewMatrix)

    const modelViewAllMatrix = getModelViewMatrix();
    device.queue.writeBuffer(geometryPipelineObj.modelViewAllBuffer, 0, modelViewAllMatrix)

    let lightMoveFrameCount = 0;
    let pointLightPosition = new Float32Array(4 * lightNUM)
    let pointColors= new Float32Array(4 * lightNUM)
    let pointLightPositionO = new Float32Array(4 * lightNUM)
    let modelViewMatrix4PointLights = new Float32Array(lightNUM * 4 * 4)
    let colorBuffer1 = new Float32Array(lightNUM * 4)

    // write matrix & colors
    device.queue.writeBuffer(emissiveGeometryPipelineObj.modelViewBuffer, 0, modelViewMatrix4PointLights)
    device.queue.writeBuffer(emissiveGeometryPipelineObj.modelViewAllBuffer, 0, modelViewAllMatrix)

    // ambient light, just 1 float32
    const ambient = new Float32Array([0.1])
    // point light, 2 x vec4: 4 position + 4 configs

    // dir light, 2 x vec4: 4 position + 4 configs
    const directionalLight = new Float32Array(8)
    // directionalLight[0] = 50
    directionalLight[1] = 50
    // directionalLight[2] = -50
    directionalLight[4] = 0.2 // intensity

    let pointLightOption = new Float32Array(4)
    pointLightOption[0] = 1; // intensity
    pointLightOption[1] = 20;// radius


    async function updatePointLightOption(lightNumber: number) {

        pointLightPosition = new Float32Array(4 * lightNumber);
        pointLightPositionO = new Float32Array(4 * lightNumber);
        pointColors = new Float32Array(4 * lightNumber);

        for (let i = 0; i < pointLightPositionO.length; i += 4) {
            pointLightPositionO[i + 0] = Math.random() * width - width / 2 // z
            pointLightPositionO[i + 1] = 10 //Math.random() * height - height / 2 // z
            pointLightPositionO[i + 2] = Math.random() * width - width / 2 // z
        }

        for (let i = 0; i < pointColors.length; i += 4) {
            pointColors[i + 0] = Math.random() 
            pointColors[i + 1] = Math.random()
            pointColors[i + 2] = Math.random()
        }

        const lightingPipelineObjInner = await createLightingPipeline(device, format, lightNumber, scene.enableShadow);

        let modelViewMatrix1Ori = modelViewMatrix4PointLights;

        modelViewMatrix4PointLights = new Float32Array(lightNumber * 4 * 4)
        colorBuffer1 = new Float32Array(lightNumber * 4)

        for (let i = 0; i < modelViewMatrix1Ori.length && i < modelViewMatrix4PointLights.length; i++) {
            modelViewMatrix4PointLights[i] = modelViewMatrix1Ori[i];
        }

        // for (let i = modelViewMatrix1Ori.length; i < modelViewMatrix4PointLights.length; i += 16) {
        //     // create simple object
        //     const position = { x: Math.random() * 40 - 20, y: Math.random() * 40 - 20, z: Math.random() * 40 - 20 }
        //     const rotation = { x: Math.random(), y: Math.random(), z: Math.random() }
        //     const scale = { x: 1, y: 1, z: 1 }
        //     const modelView = getModelViewMatrix(position, rotation, scale)
        //     modelViewMatrix4PointLights.set(modelView, i)
        // }

        emissiveGeometryPipelineObj = await createGeometryPipeline(
            device, { vertex: normal, fragment: emissive }, size, lightNumber, gbuffer, cullMode);

        scene.removeInstance(sphereEmissive);
        sphereEmissive.pipeline = emissiveGeometryPipelineObj;
        scene.addInstance(sphereEmissive, lightNumber);

        device.queue.writeBuffer(lightingPipelineObjInner.pointOptionBuffer, 0, pointLightOption)
        device.queue.writeBuffer(lightingPipelineObjInner.pointPositionBuffer, 0, pointLightPosition)
        device.queue.writeBuffer(lightingPipelineObjInner.pointColorBuffer, 0, pointColors)
        device.queue.writeBuffer(emissiveGeometryPipelineObj.colorBuffer, 0, pointColors)

        if (lightingPipelineObj) {
            lightingPipelineObj.pointOptionBuffer.destroy();
            lightingPipelineObj.pointPositionBuffer.destroy();
            lightingPipelineObj.directionalBuffer.destroy();
            lightingPipelineObj.ambientBuffer.destroy();
            lightingPipelineObj.pointColorBuffer.destroy();
        }
        scene.setLightingPipeline(lightingPipelineObjInner);
        scene.resize(size);
        // updateCamera() 
        return lightingPipelineObjInner;
    }

    gui.add(lightParams, 'ambientIntensity', 0, 1).step(0.01).name('环境光强度');
    gui.add(lightParams, 'directionalLightIntensity', 0, 1).step(0.01).name('平行光强度');
    const lightIntensityItem = gui.add(lightParams, 'pointLightIntensity', 0, 1).step(0.0001).name('点光源强度');
    gui.add(lightParams, 'pointLightRadius', 1, 100).step(1).name('点光源半径');
    gui.add(lightParams, 'pointLightNum', 1, 4000).step(1).name('点光源数量').onChange(pointLightNumChange);

    gui.add(lightParams, 'lightsMove').name('光源移动');
    gui.add(lightParams, 'lightsMoveSpeed', 0, 100).step(1).name('光源移动速度');

    gui.add(lightParams, 'enableShadow').name('开关阴影').onChange(() => {
        scene.enableShadow = lightParams.enableShadow;
    });

    async function pointLightNumChange() {
        // viewer.pause();
        lightNUM = lightParams.pointLightNum;

        // sqrtLn = Math.sqrt(lightNUM);
        // delta = 10 / sqrtLn;
        lightingPipelineObj = await updatePointLightOption(lightNUM);

        updateLightPosition();
        // lightIntensityItem.setValue(Math.min(10 / lightNUM, 1));

        // viewer.resume();
    }

    var lightingPipelineObj: any = null;
    await pointLightNumChange();
    // await updatePointLightOption(lightNUM);

    const up = vec3.fromValues(0, 1, 0)
    const origin = vec3.fromValues(0, 0, 0)
    const lightPosition = vec3.fromValues(0, 100, 0)
    const lightViewMatrix = mat4.create()
    const lightProjectionMatrix = mat4.create()

    updateLightPosition();

    viewer.start((now: number) => {
        // update lights position

        if (lightParams.lightsMove) {

            lightMoveFrameCount += lightParams.lightsMoveSpeed;

            updateLightPosition();
        }

        lightPosition[0] = directionalLight[0]
        lightPosition[1] = directionalLight[1]
        lightPosition[2] = directionalLight[2]

        // update lvp matrix
        mat4.lookAt(
            lightViewMatrix,
            lightPosition,
            origin, up
        )
        mat4.ortho(lightProjectionMatrix, -40, 40, -40, 40, -50, 200)
        mat4.multiply(lightProjectionMatrix, lightProjectionMatrix, lightViewMatrix)
        if (scene.shadowPipeline) {
            device.queue.writeBuffer(scene.shadowPipeline.lightProjectionBuffer, 0, lightProjectionMatrix as Float32Array)
        }

        ambient[0] = lightParams.ambientIntensity;
        pointLightOption[0] = lightParams.pointLightIntensity;
        pointLightOption[1] = lightParams.pointLightRadius;
        directionalLight[4] = lightParams.directionalLightIntensity;

        // update lights position & config to GPU
        device.queue.writeBuffer(lightingPipelineObj.ambientBuffer, 0, ambient)
        device.queue.writeBuffer(lightingPipelineObj.directionalBuffer, 0, directionalLight)
        device.queue.writeBuffer(lightingPipelineObj.pointPositionBuffer, 0, pointLightPosition)
        device.queue.writeBuffer(lightingPipelineObj.pointOptionBuffer, 0, pointLightOption)
        device.queue.writeBuffer(lightingPipelineObj.pointColorBuffer, 0, pointColors)

        device.queue.writeBuffer(emissiveGeometryPipelineObj.modelViewBuffer, 0, modelViewMatrix4PointLights)
    });

    function updateLightPosition() {

        const rotation = { x: Math.random(), y: Math.random(), z: Math.random() }
        const scale = { x: 2, y: 2, z: 2 }
        let sqrtLn = 60;//Math.sqrt(lightNUM);

        let angle = lightMoveFrameCount / 500;

        for (let i = 0; i < lightNUM; i++) {

            let offset = i * 4 * 4;
            if (offset < modelViewMatrix4PointLights.length) {

                const idx = i * 4;
                const dx = i % sqrtLn - sqrtLn / 2
                const dy = Math.floor(i / sqrtLn) - sqrtLn / 2
                const position = {
                    x: pointLightPositionO[idx + 0] + height * Math.sin(angle + dx), //+ dx * 3 * delta,
                    y: pointLightPositionO[idx + 1] + height / 2 * Math.cos(angle + dy), //+ dy * 3 * delta,
                    z: pointLightPositionO[idx + 2], // (i + 10) % 40 - 20,
                }

                pointLightPosition[idx + 0] = position.x;
                pointLightPosition[idx + 1] = position.y;
                pointLightPosition[idx + 2] = position.z;
                const modelView = getModelViewMatrix(position, rotation, scale)
                modelViewMatrix4PointLights.set(modelView, offset)
            }

            // break;
        }
        directionalLight[0] = 100 * Math.sin(angle)
        directionalLight[2] = 100 * Math.cos(angle)
    }


    const canvas = viewer.getCanvas()
    scene.resize(size);
    // 设置 tabindex 属性以允许 canvas 获得焦点
    canvas.setAttribute('tabindex', '0');
    canvas.focus();

    const controller = new ViewControls(scene, viewer.canvas)

    window.controller = controller;

    // updateCamera()
    // re-configure context on resize
    window.addEventListener('resize', () => {
        size.width = canvas.width = canvas.clientWidth * devicePixelRatio
        size.height = canvas.height = canvas.clientHeight * devicePixelRatio

        gbuffer = scene.resize(size);

        // update aspect
        // updateCamera()
    })
}

